package com.hmdp.service.impl;

import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.hmdp.dto.Result;
import com.hmdp.entity.SeckillVoucher;
import com.hmdp.entity.VoucherOrder;
import com.hmdp.mapper.VoucherOrderMapper;
import com.hmdp.service.ISeckillVoucherService;
import com.hmdp.service.IVoucherOrderService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.utils.RedisIdWorker;
import com.hmdp.utils.UserHolder;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.aop.framework.AopContext;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.Collections;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author 虎哥
 * @since 2021-12-22
 */
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {

    @Resource
    private ISeckillVoucherService seckillVoucherService;

    @Resource
    private RedisIdWorker redisIdWorker;
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private RedissonClient redissonClient;

    private static final DefaultRedisScript<Long> SECKILL_SCRIPT;
    //    静态代码块：可以使得首次加载便能初始化完成脚本，而无需多次的io调用
    static {
        SECKILL_SCRIPT = new DefaultRedisScript<>();
        SECKILL_SCRIPT.setLocation(new ClassPathResource("seckill.lua"));
        SECKILL_SCRIPT.setResultType(Long.class);
    }

    @Override
    public Result seckillVoucher(Long voucherId) {
        Long userId = UserHolder.getUser().getId();
//      1 执行lua脚本
        Long result = stringRedisTemplate.execute(
                SECKILL_SCRIPT,
                Collections.emptyList(),
                voucherId.toString(),
                userId.toString()
        );
        int r = result.intValue();
//      2 判断结果是否为0
//          2.1 不为0 代表没有购买资格
        if(r == 1){
            return Result.fail("库存不足");
        }
        else if(r == 2) {
            return Result.fail("当前用户重复下单");
        }
//        2.2 为0 代表有购买资格
        long orderID = redisIdWorker.nextId("order");
//        TODO 保存阻塞队列
//        3 返回订单ID

        return Result.ok(orderID);
    }

/*
未实现异步实现秒杀的代码
    @Override
    public Result seckillVoucher(Long voucherId) {
//        1. 查询优惠券信息
        SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
//        2. 判断秒杀是否开始
        if (LocalDateTime.now().isBefore(voucher.getBeginTime())) {
            return Result.fail("活动还未开始");
        }
//        3. 判断秒杀是否结束
        if (LocalDateTime.now().isAfter(voucher.getEndTime())) {
            return Result.fail("活动已结束");
        }
//        4. 判断库存是否充足
        if (voucher.getStock() < 1) {
            return Result.fail("库存不足");
        }
        Long userId = UserHolder.getUser().getId();
//        synchronized (userId.toString().intern()) { // synchronized 无法解决多tomcat问题
        // 获取代理对象（事务）
//      创建锁对象
//      由于业务是不同用户只能购买一单,所以锁的key也应该加上用户id
//        SimpleRedisLock lock = new SimpleRedisLock("order:" + userId,
//                stringRedisTemplate);
        RLock lock = redissonClient.getLock("lock:order:" + userId);
        boolean isLock = lock.tryLock();
        if (!isLock) {
            return Result.fail("一个用户只能下一单");
        }
//      由于解锁的前面步骤可能发生异常，所以需要需要使用try-catch,在finally中解锁
        try {
            IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
            return proxy.createVoucherOrder(voucherId);
        } catch (IllegalStateException e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }
//        }
    }

*/


    @Override
    @Transactional
    public Result createVoucherOrder(Long voucherId) {
        //        5. 一人一单

        Long userId = UserHolder.getUser().getId();
//        5.1. 查询订单
        int count = query()
                .eq("user_id", userId)
                .eq("voucher_id", voucherId)
                .count();
//        5.2. 判断订单是否已存在
        if (count > 0) {
            return Result.fail("用户已经购买过一单了");
        }

//        6. 扣减库存
        //
        //第一个 update()seckillVoucherService.update() 是 MyBatis-Plus 的链式更新器（ChainUpdate） 的入口方法。
        //它返回一个 UpdateChainWrapper 对象，用来链式构造更新语句。
        //第二个 update()方法是 UpdateChainWrapper 对象中的执行方法，表示真正触发 SQL 更新操作。
        //
        boolean success = seckillVoucherService
                .update()
                .eq("voucher_id", voucherId) // 返回一个wrapper
                .gt("stock", 0) // 乐观锁
                .setSql("stock = stock - 1") //自定义sql
                .update(); // 交给mapper执行更新
        if (!success) {
            return Result.fail("库存不足");
        }
//        6. 创建订单
        VoucherOrder voucherOrder = new VoucherOrder();
//        6.1 写入订单id
        long orderId = redisIdWorker.nextId("shop");
        voucherOrder.setId(orderId);
//        6.2 写入用户id
        voucherOrder.setUserId(userId);
//        6.3 写入代金券id
        voucherOrder.setVoucherId(voucherId);
//        6.4 写入该订单到数据库
        save(voucherOrder);

//        7. 返回订单id
        return Result.ok(orderId);

    }
}

